﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using P2P.Main.Enums;
using P2P.Main.Model;
using P2P.Main.Session;
using P2P.Network.ExtensionMethods;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace P2P.Main.Handler
{
    /// <summary>
    /// 客户端NAT检测Handler
    /// </summary>
    public class NatCheckHandler
    {
        private ApiProtocol apiProtocol;

        public NatCheckHandler(ApiProtocol apiProtocol)
        {
            this.apiProtocol = apiProtocol;
        }

        #region NAT类型检测步骤

        /// <summary>
        /// 第一步：检测客户端NAT类型是否是对称NAT
        /// 若为对称NAT，则检测结束
        /// 若不是对称NAT，则继续检测是否是全锥型NAT
        /// </summary>
        public void Step1() 
         {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null) 
            {
                return;
            }

            string localIP = (string)apiProtocol.Params["LocalIP"];
            int localPort = (int)apiProtocol.Params["LocalPort"];

            if (!activeNode.IP.Equals(localIP) || activeNode.UdpPublicPort != localPort)
            {
                ActiveNodeSessionManager.Instance.Flush(AccountId, NatType.SYMMETRIC);
                //Nat类型检测结束
                //当前Nat类型为对称型Nat
                JObject data = new JObject();
                data.Add("Name", "NatCheck");
                data.Add("Method", "NatCheckFinished");
                data.Add("Version", "1.0");

                JObject param = new JObject();
                param.Add("NatType", (int)NatType.SYMMETRIC);
                data.Add("Params", param);

                string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

                apiProtocol.Socket.SendTo(ack,activeNode.IP, activeNode.UdpPublicPort);
            }
            else
            {
                //继续检测
                //判断Nat类型是否为全锥形Nat
                JObject data = new JObject();
                data.Add("Name", "NatCheck");
                data.Add("Method", "Step1");
                data.Add("Version", "1.0");

                JObject param = new JObject();
                param.Add("NatType", (int)NatType.UNKNOW);
                data.Add("Params", param);
                string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

                Task.Run(() => 
                {
                    bool exit = false;
                    while (!exit) 
                    {
                        try
                        {
                            //启用服务端第二个IP和随机端口
                            //发送数据包给客户端公网IP和监听的端口
                            //若，客户端收到数据，则客户端为全锥形Nat
                            Random r = new Random();
                            int randomPort = r.Next(6000, 6500);

                            string localIP = SharedModel.Instance.Hosts.UDPMainServerSecondInternalIP;

                            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                            sock.Bind(new IPEndPoint(IPAddress.Parse(localIP), randomPort));

                            sock.SendTo(ack, activeNode.IP, activeNode.UdpPublicPort);

                            sock.Close();

                            exit = true;
                        }
                        catch{ }
                    }
                   
                });

                JObject deepData = (JObject)data.DeepClone();
                deepData["Method"] = "Step2";

                apiProtocol.Socket.SendTo(
                    JsonConvert.SerializeObject(R<JObject>.Ok(deepData)), activeNode.IP, activeNode.UdpPublicPort);
            }
        }

        /// <summary>
        /// 第二步：检测完成更新NAT类型为全锥型NAT
        /// </summary>
        public void Step2() 
        {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null)
            {
                return;
            }
            //检测结束
            //刷新Nat为全锥形Nat
            //检测结束
            ActiveNodeSessionManager.Instance.Flush(AccountId, NatType.FULLCONE);

            JObject data = new JObject();
            data.Add("Name", "NatCheck");
            data.Add("Method", "NatCheckFinished");
            data.Add("Version", "1.0");

            JObject param = new JObject();
            param.Add("NatType", (int)NatType.FULLCONE);
            data.Add("Params", param);

            string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

            apiProtocol.Socket.SendTo(ack, apiProtocol.RemoteEndPoint);

        }

        /// <summary>
        /// 第三步：检测NAT类型是否是IP受限锥型Nat
        /// </summary>
        public void Step3() 
        {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null)
            {
                return;
            }

            JObject data = new JObject();
            data.Add("Name", "NatCheck");
            data.Add("Method", "Step3");
            data.Add("Version", "1.0");

            JObject param = new JObject();
            param.Add("NatType", (int)NatType.UNKNOW);
            data.Add("Params", param);
            string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

            Task.Run(() =>
            {
                bool exit = false;
                while (!exit)
                {
                    try
                    {
                        //启用服务端第一个已经与客户端通讯过的IP和随机端口
                        //发送数据包给客户端公网IP和监听的端口
                        //若，客户端收到数据，则客户端为IP受限锥型Nat
                        Random r = new Random();
                        int randomPort = r.Next(6000, 6500);

                        string localIP = SharedModel.Instance.Hosts.UDPMainServerFirstInternalIP;

                        Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                        sock.Bind(new IPEndPoint(IPAddress.Parse(localIP), randomPort));

                        sock.SendTo(ack, activeNode.IP, activeNode.UdpPublicPort);

                        sock.Close();

                        exit = true;
                    }
                    catch { }
                }

            });

            JObject deepClone = (JObject)data.DeepClone();
            deepClone["Method"] = "Step4";
            apiProtocol.Socket.SendTo(JsonConvert.SerializeObject(R<JObject>.Ok(deepClone)), apiProtocol.RemoteEndPoint);
        }

        /// <summary>
        /// 第四步：更新NAT类型为IP受限锥型NAT 
        /// </summary>
        public void Step4() 
        {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null)
            {
                return;
            }
            //检测结束
            //刷新Nat为IP受限锥形Nat
            //检测结束
            ActiveNodeSessionManager.Instance.Flush(AccountId, NatType.RESTRICTEDCONE);

            JObject data = new JObject();
            data.Add("Name", "NatCheck");
            data.Add("Method", "NatCheckFinished");
            data.Add("Version", "1.0");

            JObject param = new JObject();
            param.Add("NatType", (int)NatType.RESTRICTEDCONE);
            data.Add("Params", param);

            string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

            apiProtocol.Socket.SendTo(ack, apiProtocol.RemoteEndPoint);
        }

        /// <summary>
        /// 第五步：检测NAT类型是否是端口受限锥型NAT
        /// </summary>
        public void Step5() 
        {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null)
            {
                return;
            }

            JObject data = new JObject();
            data.Add("Name", "NatCheck");
            data.Add("Method", "Step5");
            data.Add("Version", "1.0");

            JObject param = new JObject();
            param.Add("NatType", (int)NatType.UNKNOW);
            data.Add("Params", param);
            string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

            Task.Run(() =>
            {
                bool exit = false;
                while (!exit)
                {
                    try
                    { 
                        //启用服务端第二个IP和第二个端口给客户端发送数据包
                        //若，客户端收到了数据包，则Nat类型为端口受限锥形Nat
                        string secondIP = SharedModel.Instance.Hosts.UDPMainServerSecondInternalIP;
                        int secondPort = SharedModel.Instance.Hosts.UDPMainServerSecondInternalPort.Value;

                        Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                        sock.Bind(new IPEndPoint(IPAddress.Parse(secondIP), secondPort));

                        sock.SendTo(ack, activeNode.IP, activeNode.UdpPublicPort);

                        sock.Close();

                        exit = true;
                    }
                    catch { }
                }

            });

            JObject deepClone = (JObject)data.DeepClone();
            deepClone["Method"] = "Step6";
            apiProtocol.Socket.SendTo(JsonConvert.SerializeObject(R<JObject>.Ok(deepClone)), apiProtocol.RemoteEndPoint);
        }

        /// <summary>
        /// 第六步：更新NAT类型为端口受限锥型NAT 
        /// </summary>
        public void Step6() 
        {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null)
            {
                return;
            }
            //检测结束
            //刷新Nat为端口受限锥形Nat
            //检测结束
            ActiveNodeSessionManager.Instance.Flush(AccountId, NatType.PORTRESTRICTEDCONE);

            JObject data = new JObject();
            data.Add("Name", "NatCheck");
            data.Add("Method", "NatCheckFinished");
            data.Add("Version", "1.0");

            JObject param = new JObject();
            param.Add("NatType", (int)NatType.PORTRESTRICTEDCONE);
            data.Add("Params", param);

            string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

            apiProtocol.Socket.SendTo(ack, apiProtocol.RemoteEndPoint);
        }

        /// <summary>
        /// 第七步：检测结束，NAT类型未知
        /// </summary>
        public void Step7() 
        {
            string AccountId = (string)apiProtocol.Params["AccountId"];

            ActiveNode activeNode = ActiveNodeSessionManager.Instance.Find(AccountId);

            if (activeNode == null)
            {
                return;
            }
            //检测结束
            //刷新Nat为未知Nat类型
            //检测结束
            ActiveNodeSessionManager.Instance.Flush(AccountId, NatType.UNKNOW);

            JObject data = new JObject();
            data.Add("Name", "NatCheck");
            data.Add("Method", "NatCheckFinished");
            data.Add("Version", "1.0");

            JObject param = new JObject();
            param.Add("NatType", (int)NatType.UNKNOW);
            data.Add("Params", param);

            string ack = JsonConvert.SerializeObject(R<JObject>.Ok(data));

            apiProtocol.Socket.SendTo(ack, apiProtocol.RemoteEndPoint);
        }
        #endregion 

    }
}
